<!DOCTYPE html>
<html class="has-navbar-fixed-top">
<head>
    <meta charset="utf-8">
<title>集合 - wanzixin</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/outdated-browser/1.1.5/outdatedbrowser.min.css">


<link href="/Study/Java/zh-cn/%E9%9B%86%E5%90%88/" rel="alternate" hreflang="en" />
    


<meta name="description" content="">





    <meta name="description" content="本节我们介绍Java的集合类型。集合类型也是Java标准库中被使用最多的类型。">
<meta property="og:type" content="article">
<meta property="og:title" content="集合">
<meta property="og:url" content="https://wanzixin.github.io/Study/Java/zh-cn/%E9%9B%86%E5%90%88/index.html">
<meta property="og:site_name" content="wanzixin">
<meta property="og:description" content="本节我们介绍Java的集合类型。集合类型也是Java标准库中被使用最多的类型。">
<meta property="og:locale" content="zh_CN">
<meta property="article:published_time" content="2021-05-02T09:16:25.000Z">
<meta property="article:modified_time" content="2021-05-30T13:33:30.326Z">
<meta property="article:author" content="wanzixin">
<meta name="twitter:card" content="summary">





<link rel="icon" href="/favicon.png">


<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Ovo|Source+Code+Pro">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/bulma/0.6.2/css/bulma.min.css">


<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/lightgallery/1.6.8/css/lightgallery.min.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/justifiedGallery/3.6.5/css/justifiedGallery.min.css">


<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/atom-one-light.min.css">


<link rel="stylesheet" href="/css/style.css">


<script defer src="//use.fontawesome.com/releases/v5.0.8/js/all.js"></script>


    
    
    
    
    
    
    
    
    
    

    


<meta name="generator" content="Hexo 5.4.0"></head>
<body>
    
<nav class="navbar is-transparent is-fixed-top navbar-main" role="navigation" aria-label="main navigation">
    <div class="container">
        <div class="navbar-brand">
            <a class="navbar-item navbar-logo" href="/zh-cn">
                
                    
                    wanzixin
                    
                
            </a>
            <div class="navbar-burger">
                <span></span>
                <span></span>
                <span></span>
            </div>
        </div>
        
        <div class="navbar-menu navbar-start">
            
            <a class="navbar-item "
               href="/archives">Archives</a>
            
            <a class="navbar-item "
               href="/categories">Categories</a>
            
            <a class="navbar-item "
               href="/categories/Diary">Diary</a>
            
            <a class="navbar-item "
               href="/categories/Gallery">Gallery</a>
            
            <a class="navbar-item "
               href="/categories/Study">Study</a>
            
            <a class="navbar-item "
               href="/categories/Item">Item</a>
            
            <a class="navbar-item "
               href="/about">About</a>
            
        </div>
        
        <div class="navbar-menu navbar-end">
            
            <a class="navbar-item search" title="搜索" href="javascript:;">
                <i class="fas fa-search"></i>
            </a>
            
            
            <div class="navbar-item is-hoverable has-dropdown is-hidden-mobile is-hidden-tablet-only toc">
                <a class="navbar-item" title="目录">
                    <i class="fa fa-list"></i>
                </a>
                <div class="navbar-dropdown is-right">
                    
                    
                    
                    
                    <a class="navbar-item" href="#Java集合简介">1&nbsp;&nbsp;<b>Java集合简介</b></a>
                    
                    
                    
                    <a class="navbar-item" href="#Collection">1.1&nbsp;&nbsp;Collection</a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#使用List">2&nbsp;&nbsp;<b>使用List</b></a>
                    
                    
                    
                    <a class="navbar-item" href="#List的特点">2.1&nbsp;&nbsp;List的特点</a>
                    
                    
                    
                    <a class="navbar-item" href="#创建List">2.2&nbsp;&nbsp;创建List</a>
                    
                    
                    
                    <a class="navbar-item" href="#遍历List">2.3&nbsp;&nbsp;遍历List</a>
                    
                    
                    
                    <a class="navbar-item" href="#List和Array转换">2.4&nbsp;&nbsp;List和Array转换</a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#编写equals方法">3&nbsp;&nbsp;<b>编写equals方法</b></a>
                    
                    
                    
                    <a class="navbar-item" href="#编写equals">3.1&nbsp;&nbsp;编写equals</a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#使用Map">4&nbsp;&nbsp;<b>使用Map</b></a>
                    
                    
                    
                    <a class="navbar-item" href="#遍历Map">4.1&nbsp;&nbsp;遍历Map</a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#编写equals和hashCode">5&nbsp;&nbsp;<b>编写equals和hashCode</b></a>
                    
                    
                    
                    <a class="navbar-item" href="#延伸阅读">5.1&nbsp;&nbsp;延伸阅读</a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#使用EnumMap">6&nbsp;&nbsp;<b>使用EnumMap</b></a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#使用TreeMap">7&nbsp;&nbsp;<b>使用TreeMap</b></a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#使用Properties">8&nbsp;&nbsp;<b>使用Properties</b></a>
                    
                    
                    
                    <a class="navbar-item" href="#读取配置文件">8.1&nbsp;&nbsp;读取配置文件</a>
                    
                    
                    
                    <a class="navbar-item" href="#写入配置文件">8.2&nbsp;&nbsp;写入配置文件</a>
                    
                    
                    
                    <a class="navbar-item" href="#编码">8.3&nbsp;&nbsp;编码</a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#使用Set">9&nbsp;&nbsp;<b>使用Set</b></a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#使用Queue">10&nbsp;&nbsp;<b>使用Queue</b></a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#使用PriorityQueue">11&nbsp;&nbsp;<b>使用PriorityQueue</b></a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#使用Deque">12&nbsp;&nbsp;<b>使用Deque</b></a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#使用Stack">13&nbsp;&nbsp;<b>使用Stack</b></a>
                    
                    
                    
                    <a class="navbar-item" href="#Stack的作用">13.1&nbsp;&nbsp;Stack的作用</a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#使用Iterator">14&nbsp;&nbsp;<b>使用Iterator</b></a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#使用Collections">15&nbsp;&nbsp;<b>使用Collections</b></a>
                    
                    
                    
                    <a class="navbar-item" href="#创建空集合">15.1&nbsp;&nbsp;创建空集合</a>
                    
                    
                    
                    <a class="navbar-item" href="#创建单元素集合">15.2&nbsp;&nbsp;创建单元素集合</a>
                    
                    
                    
                    <a class="navbar-item" href="#排序">15.3&nbsp;&nbsp;排序</a>
                    
                    
                    
                    <a class="navbar-item" href="#洗牌">15.4&nbsp;&nbsp;洗牌</a>
                    
                    
                    
                    <a class="navbar-item" href="#不可变集合">15.5&nbsp;&nbsp;不可变集合</a>
                    
                    
                    
                    <a class="navbar-item" href="#线程安全集合">15.6&nbsp;&nbsp;线程安全集合</a>
                    
                </div>
            </div>
            
            
            <a class="navbar-item" title="GitHub" target="_blank" rel="noopener" href="https://github.com/wanzixin">
                
                <i class="fab fa-github"></i>
                
            </a>
               
            
        </div>
    </div>
</nav>

    <section class="section">
    <div class="container">
    <article class="article content gallery" itemscope itemprop="blogPost">
    <h1 class="article-title is-size-3 is-size-4-mobile" itemprop="name">
        
            集合
        
    </h1>
    <div class="article-meta columns is-variable is-1 is-multiline is-mobile is-size-7-mobile">
        <span class="column is-narrow">
            
                <span>5月 2 2021</span>
            
        </span>
        
        <span class="column is-narrow article-category">
            <i class="far fa-folder"></i>
            <a class="article-category-link" href="/zh-cn/categories/Study/">Study</a><span>></span><a class="article-category-link" href="/zh-cn/categories/Study/Java/">Java</a>
        </span>
        
        
        <span class="column is-narrow">
            
            
            1 小时 读完 (约 12400 字)
        </span>
        
    </div>
    <div class="article-entry is-size-6-mobile" itemprop="articleBody">
    
        <html><head></head><body><p>本节我们介绍Java的集合类型。集合类型也是Java标准库中被使用最多的类型。<span id="more"></span></p>
<h2 id="Java集合简介"><a href="#Java集合简介" class="headerlink" title="Java集合简介"></a>Java集合简介</h2><p>什么是集合（Collection）？集合就是由若干个确定的元素所构成的整体。为什么要在计算机中引入集合呢？这是为了便于处理一组类似的数据。</p>
<p>在Java中，如果一个Java对象可以在内部持有若干其他Java对象，并对外提供访问接口，我们把这种Java对象称为集合。很显然，Java的数组可以看作是一种集合。既然Java提供了数组这种数据类型，可以充当集合，那么我们为什么还需要其他集合类？这是因为数组有如下限制：</p>
<ul>
<li>数组初始化后大小不可变</li>
<li>数组只能按索引顺序存取</li>
</ul>
<p>因此，我们需要各种不同类型的集合类来处理不同的数据，例如：</p>
<ul>
<li>可变大小的顺序链表</li>
<li>保证无重复元素的集合</li>
<li>…</li>
</ul>
<h3 id="Collection"><a href="#Collection" class="headerlink" title="Collection"></a>Collection</h3><p>Java标准库自带的java.util包提供了集合类：Collection，它是除Map外所有其他集合类的根接口。Java的java.util包主要提供了以下三种类型的集合：</p>
<ul>
<li>List：一种有序列表的集合</li>
<li>Set：一种保证没有重复元素的集合</li>
<li>Map：一种通过键值（key-value）查找的映射表集合</li>
</ul>
<p>Java的集合设计有几个特点：一是实现了接口和实现类相分离，例如，有序表的接口是List，具体的实现类有ArrayList，LinkedList等；二是支持泛型，我们可以限制在一个集合中只能放入同一数据类型的元素，例如，<code>List&lt;String&gt; list = new ArrayList&lt;&gt;(); // 只能放入String类型</code>；最后，Java访问集合总是通过统一的方式—-迭代器（Iterator）来实现，它最明显的好处在于无需知道集合内部元素是按什么方式存储的。</p>
<p>由于Java的集合设计非常久远，中间经历过大规模改进，我们要注意到有一小部分集合类是遗留类，不应该继续使用：</p>
<ul>
<li>Hashtable：一种线程安全的Map实现</li>
<li>Vector：一种线程安全的List实现</li>
<li>Stack：基于Vector实现的LIFO的栈</li>
</ul>
<p>还有一小部分接口是遗留接口，也不应该继续使用：</p>
<ul>
<li>Enumeration<e>：已被Iterator<e>取代</e></e></li>
</ul>
<h2 id="使用List"><a href="#使用List" class="headerlink" title="使用List"></a>使用List</h2><p>在集合类中，List是最基础的一种集合，它是一种有序列表。List的行为和数组几乎完全相同，List内部按照放入元素的先后顺序存放，每个元素都可以通过索引确定自己的位置，List的索引和数组一样，都是从0开始。但在添加和删除元素的时候，使用数组实现会非常麻烦。在实际应用中，需要增删元素的有序列表，我们使用最多的是ArrayList。实际上，ArrayList在内部使用了数组来存储所有的元素。</p>
<p>例如，一个ArrayList拥有5个元素，实际数组大小为6（即有一个空位）。当添加一个元素并指定索引到ArrayList时，ArrayList自动移动需要移动的元素。然后，往内部指定索引的数组位置添加一个元素，然后把size加1。继续添加元素，但是数组已满，没有空闲位置的时候，ArrayList先创建一个更大的新数组，然后把旧数组的所有元素复制到新数组，紧接着用新数组取代旧数组。现在，新数组就有了空位，可以继续添加一个元素到数组末尾，同时size加1。</p>
<p>可见，ArrayList把添加和删除的操作封装起来，让我们操作List类似于操作数组，却不用关心内部元素如何移动。</p>
<p>我们考察List<e>接口，可以看到几个主要的接口方法：</e></p>
<ul>
<li>在末尾添加一个元素：boolean add(E e)</li>
<li>在指定索引处添加一个元素：boolean add(int index, E e)</li>
<li>删除指定索引的元素：E remove(int index)</li>
<li>删除某个元素：boolean remove(Object e)</li>
<li>获取指定索引的元素：E get(int index)</li>
<li>获取链表大小：int size()</li>
</ul>
<p>但是，实现List接口并非只能通过数组来实现，另一种LinkedList通过“链表”也实现了List接口。在LinkedList中，它的内部元素都指向下一个元素。</p>
<p>通常情况下，我们总是优先使用ArrayList。</p>
<h3 id="List的特点"><a href="#List的特点" class="headerlink" title="List的特点"></a>List的特点</h3><p>使用List时，我们要关注List接口的规范。List接口允许我们添加重复的元素，即List内部的元素可以重复。List还允许添加null。</p>
<h3 id="创建List"><a href="#创建List" class="headerlink" title="创建List"></a>创建List</h3><p>除了使用ArrayList和LinkedList，我们还可以通过List接口提供的of()方法，根据给定元素快速创建List。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">List&lt;Integer&gt; list = List.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">5</span>);<br></code></pre></td></tr></tbody></table></figure>

<p>但是List.of()方法不接受null值，如果传入null，会抛出NullPointerException异常。</p>
<h3 id="遍历List"><a href="#遍历List" class="headerlink" title="遍历List"></a>遍历List</h3><p>和数组类型一样，我们要遍历一个List，完全可以用for循环根据索引配合get(int)方法遍历。但不推荐这种方式，一是代码复杂，二是get(int)方法只有ArrayList的实现是高效的，换成LinkedList后，索引越大，访问速度越慢。</p>
<p>所以我们要始终坚持使用迭代器Iterator来访问List。Iterator本身也是一个对象，但它是由List的实例调用iterator()方法的时候创建的。Iterator对象知道如何遍历一个List，并且不同的List类型，返回的Iterator对象实现也是不同的，但总是具有最高访问效率。</p>
<p>Iterator对象有两个方法：<code>boolean hasNext()</code>判断是否有下一个元素，<code>E next()</code>返回下一个元素。因此，使用Iterator遍历List代码如下：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java">List&lt;String&gt; list = List.of(<span class="hljs-string">"apple"</span>, <span class="hljs-string">"pear"</span>, <span class="hljs-string">"banana"</span>);<br>    <span class="hljs-keyword">for</span> (Iterator&lt;String&gt; it = list.iterator(); it.hasNext(); ) {<br>    String s = it.next();<br>    System.out.println(s);<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>有同学可能觉得使用Iterator访问List的代码比使用索引更复杂。但是，要记住，通过Iterator遍历List永远是最高效的方式。并且，由于Iterator遍历是如此常用，所以Java的<code>for each</code>循环本身就可以帮我们使用Iterator遍历。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java">List&lt;String&gt; list = List.of(<span class="hljs-string">"apple"</span>, <span class="hljs-string">"pear"</span>, <span class="hljs-string">"banana"</span>);<br>    <span class="hljs-keyword">for</span> (String s : list) {<br>    System.out.println(s);<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>上述代码就是我们编写遍历List的常见代码。</p>
<p>实际上，只要实现了Iterator接口的集合类都可以直接用for each循环来遍历，Java编译器本身并不知道如何遍历集合对象，但它会自动把for each循环变成Iterator的调用，原因就在于Iterator接口定义了一个Iterator<e> iterator()方法，强迫集合类必须返回一个Iterator实例。</e></p>
<h3 id="List和Array转换"><a href="#List和Array转换" class="headerlink" title="List和Array转换"></a>List和Array转换</h3><p>把List变为Array有三种方法，第一种是调用toArray()方法直接返回一个Object[]数组，这种方法会丢失类型信息，所以实际应用很少。</p>
<p>第二种是给toArray(T[])传入一个类型相同的array，List自动把元素复制到传入的array中。但是如果我们传入的数组大小和List实际的元素个数不一致怎么半？根据List接口的文档，我们知道：如果传入的数组不够大，那么List内部会创建一个新的刚好够大的数组，填充后返回；如果传入的数组比List元素还要多，那么填充完元素后，剩下的数组元素一律填充null。实际上，最常用的是传入一个恰好大小的数组。</p>
<p>最后一种更简洁的写法是通过<code>List</code>接口定义的<code>T[] toArray(IntFunction&lt;T[]&gt; generator)</code>方法。这种函数式的写法我们会在后续讲到。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 第一种</span><br>List&lt;String&gt; list = List.of(<span class="hljs-string">"apple"</span>, <span class="hljs-string">"pear"</span>, <span class="hljs-string">"banana"</span>);<br>Object[] array = list.toArray();<br><span class="hljs-keyword">for</span> (Object s : array) {<br>    System.out.println(s);<br>}<br><span class="hljs-comment">// 第二种</span><br>List&lt;Integer&gt; list = List.of(<span class="hljs-number">12</span>, <span class="hljs-number">34</span>, <span class="hljs-number">56</span>);<br>Integer[] array = list.toArray(<span class="hljs-keyword">new</span> Integer[<span class="hljs-number">3</span>]);<br><span class="hljs-keyword">for</span> (Integer n : array) {<br>    System.out.println(n);<br>}<br><span class="hljs-comment">// 第二种 改进</span><br>Integer[] array = list.toArray(<span class="hljs-keyword">new</span> Integer[list.size()]);<br><span class="hljs-comment">// 第三种</span><br>Integer[] array = list.toArray(Integer[]::<span class="hljs-keyword">new</span>);<br></code></pre></td></tr></tbody></table></figure>

<p>反过来，把Array变成List就简单多了。通过<code>List.of(T...)</code>方法最简单。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java">Integer[] array = { <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span> };<br>List&lt;Integer&gt; list = List.of(array);<br></code></pre></td></tr></tbody></table></figure>

<p>对于JDK 11以前的版本，可以使用<code>Array.asList(T...)</code>方法把数组转换为List。</p>
<p>要注意的是，返回的List不一定就是ArrayList或者LinkedList，因为List只是一个接口，如果我们调用List.of()，它返回的是一个只读List。对只读List调用add()，remove()方法会抛出<code>UnsupportedOperationException</code>。</p>
<h2 id="编写equals方法"><a href="#编写equals方法" class="headerlink" title="编写equals方法"></a>编写equals方法</h2><p>我们知道List是一种有序链表：List内部按照放入元素的先后顺序存放，并且每个元素都可以通过索引确定自己的位置。</p>
<p>List还提供了<code>boolean contains(Object o)</code>方法来判断List是否包含某个指定元素。此外，<code>int indexOf(Object o)</code>方法可以返回某个元素的索引，如果元素不存在，就返回-1。</p>
<p>这里我们注意一个问题，我们往List里添加的”C”和调用contains(“C”)传入的”C”是不是同一个实例？</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java">List&lt;String&gt; list = List.of(<span class="hljs-string">"A"</span>, <span class="hljs-string">"B"</span>, <span class="hljs-string">"C"</span>);<br>System.out.println(list.contains(<span class="hljs-keyword">new</span> String(<span class="hljs-string">"C"</span>))); <span class="hljs-comment">// true or false? true</span><br>System.out.println(list.indexOf(<span class="hljs-keyword">new</span> String(<span class="hljs-string">"C"</span>))); <span class="hljs-comment">// 2 or -1? 2</span><br></code></pre></td></tr></tbody></table></figure>

<p>我们传入的是<code>new String("C")</code>，所以一定是不同的实例。结果仍然是true，这是为什么呢？因为List内部并不是通过==判断两个元素是否相等，而是使用equals()方法判断两个元素是否相等。对于引用字段比较，我们使用equals()，对于基本类型字段的比较，我们使用==。</p>
<p>因此，要正确使用List的contains()，indexOf()这些方法，放入的实例必须正确覆写equals()方法，否则查不到放进去的实例。我们之所以能正常放入String，Integer这些对象，是因为Java标准库定义的这些类已经正确实现了equals()方法。我们来测试一下。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">import</span> java.util.List;<br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Main</span> </span>{<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{<br>        List&lt;Person&gt; list = List.of(<br>            <span class="hljs-keyword">new</span> Person(<span class="hljs-string">"Xiao Ming"</span>),<br>            <span class="hljs-keyword">new</span> Person(<span class="hljs-string">"Xiao Hong"</span>),<br>            <span class="hljs-keyword">new</span> Person(<span class="hljs-string">"Bob"</span>)<br>        );<br>        System.out.println(list.contains(<span class="hljs-keyword">new</span> Person(<span class="hljs-string">"Bob"</span>))); <span class="hljs-comment">// false</span><br>    }<br>}<br><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Person</span> </span>{<br>    String name;<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Person</span><span class="hljs-params">(String name)</span> </span>{<br>        <span class="hljs-keyword">this</span>.name = name;<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<h3 id="编写equals"><a href="#编写equals" class="headerlink" title="编写equals"></a>编写equals</h3><p>如何正确编写equals()方法？equals()方法要求必须满足以下条件：</p>
<ul>
<li>自反性（Reflexive）：对于非null的x来说，x.equals(x)必须返回true；</li>
<li>对称性（Symmetric）：对于非null的x和y来说，如果x.equals(y)为true，则y.equals(x)也必须为true；</li>
<li>传递性（Transitive）：对于非null的x、y和z来说，如果x.equals(y)为true，y.equals(z)也为true，那么x.equals(z)也必须为true；</li>
<li>一致性（Consistent）：对于非null的x和y来说，只要x和y状态不变，则x.equals(y)总是一致地返回true或者false；</li>
<li>对null的比较：即x.equals(null)永远返回false。</li>
</ul>
<p>上述规则看上去很复杂，但其实代码实现equals()方法是很简单的，以Person类为例。首先我们要定义“相等”的逻辑含义。对于Person类，如果name相等，并且age相等，我们就认为两个Person实例相等。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Person</span> </span>{<br>    <span class="hljs-keyword">public</span> String name;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> age;<br>    <br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">equals</span><span class="hljs-params">(Object o)</span> </span>{<br>        <span class="hljs-keyword">if</span> (o <span class="hljs-keyword">instanceof</span> Person) {<br>            Person p = (Person) o;<br>            <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.name.equals(p.name) &amp;&amp; <span class="hljs-keyword">this</span>.age == p.age;<br>        }<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;<br>	}<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>如果this.name为null，那么equals()方法会报错，因此需要改写。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">equals</span><span class="hljs-params">(Object o)</span> </span>{<br>    <span class="hljs-keyword">if</span> (o <span class="hljs-keyword">instanceof</span> Person) {<br>        Person p = (Person) o;<br>        <span class="hljs-keyword">boolean</span> nameEquals = <span class="hljs-keyword">false</span>;<br>        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.name == <span class="hljs-keyword">null</span> &amp;&amp; p.name == <span class="hljs-keyword">null</span>) {<br>            nameEquals = <span class="hljs-keyword">true</span>;<br>        }<br>        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.name != <span class="hljs-keyword">null</span>) {<br>            nameEquals = <span class="hljs-keyword">this</span>.name.equals(p.name);<br>        }<br>        <span class="hljs-keyword">return</span> nameEquals &amp;&amp; <span class="hljs-keyword">this</span>.age == p.age;<br>    }<br>    <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>如果Person有好几个引用类型的字段，上面的写法就太复杂了。要简化引用类型的比较，我们使用<code>Objects.equals()</code>静态方法。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">equals</span><span class="hljs-params">(Object o)</span> </span>{<br>    <span class="hljs-keyword">if</span> (o <span class="hljs-keyword">instanceof</span> Person) {<br>        Person p = (Person) o;<br>        <span class="hljs-keyword">return</span> Objects.equals(<span class="hljs-keyword">this</span>.name, p.name) &amp;&amp; <span class="hljs-keyword">this</span>.age == p.age;<br>    }<br>    <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>因此，我们总结一下equals()的正确编写方法：</p>
<ol>
<li>先确定实例“相等”的逻辑，即哪些字段相等，就认为实例相等；</li>
<li>用instanceof判断传入的待比较的Object是不是当前类型，如果是，继续比较，否则，返回false；</li>
<li>对引用类型用Objects.equals()比较，对基本类型直接用==比较。</li>
</ol>
<p>使用Objects.equals()比较两个引用类型是否相等的目的是省去了判断null的麻烦。两个引用类型都是null时它们也是相等的。</p>
<p>如果不在List种查找元素，即不调用List的contains()、indexOf()这些方法，那么放入的元素就不需要实现equals()方法。</p>
<h2 id="使用Map"><a href="#使用Map" class="headerlink" title="使用Map"></a>使用Map</h2><p>我们知道，List是一种顺序列表，如果有一个存储学生Student实例的List，要在List种根据name查找某个指定的Student分数，应该怎么办？最简单的方法是遍历List并判断name是否相等，然后返回指定元素。</p>
<p>这种需求其实非常常见，即通过一个键去查询对应的值。使用List来实现存在效率非常低的问题，因为平均需要扫描一半的元素才能确定。而Map这种键值（key-value）映射表的数据结构，作用就是能高校通过keyu快速查找value（元素）。</p>
<p>用Map来实现根据name查询某个student的代码如下。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">import</span> java.util.HashMap;<br><span class="hljs-keyword">import</span> java.util.Map;<br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Main</span> </span>{<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{<br>        Student s = <span class="hljs-keyword">new</span> Student(<span class="hljs-string">"Xiao Ming"</span>, <span class="hljs-number">99</span>);<br>        Map&lt;String, Student&gt; map = <span class="hljs-keyword">new</span> HashMap&lt;&gt;();<br>        map.put(<span class="hljs-string">"Xiao Ming"</span>, s); <span class="hljs-comment">// 将"Xiao Ming"和Student实例映射并关联</span><br>        Student target = map.get(<span class="hljs-string">"Xiao Ming"</span>); <span class="hljs-comment">// 通过key查找并返回映射的Student实例</span><br>        System.out.println(target == s); <span class="hljs-comment">// true，同一个实例</span><br>        System.out.println(target.score); <span class="hljs-comment">// 99</span><br>        Student another = map.get(<span class="hljs-string">"Bob"</span>); <span class="hljs-comment">// 通过另一个key查找</span><br>        System.out.println(another); <span class="hljs-comment">// 未找到返回null</span><br>    }<br>}<br><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Student</span> </span>{<br>    <span class="hljs-keyword">public</span> String name;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> score;<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Student</span><span class="hljs-params">(String name, <span class="hljs-keyword">int</span> score)</span> </span>{<br>        <span class="hljs-keyword">this</span>.name = name;<br>        <span class="hljs-keyword">this</span>.score = score;<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>通过上述代码可知：Map&lt;K, V&gt;是一种键值映射表，当我们调用put(K key, V value)方法时，就把key和value做了映射并放入Map。当我们调用<code>V get(K key)</code>时，就可以通过key获取对应的value。如果key不存在，则返回null。和List类似，Map也是一个接口，最常用的实现类是HashMap。</p>
<p>如果想查询某个key是否存在，可以调用<code>boolean containsKey(K key)</code>方法。</p>
<p>如果我们在存储Map映射关系的时候，对同一个key调用两次put()方法，分别放入不同的value，会出现什么问题呢？答：重复放入key-value并不会有任何问题，但是一个key只能关联一个value。实际上，put()方法的签名是V put(K key, V value)，如果放入的key已经存在，put()方法会返回被删除的旧的value，否则，返回null。始终牢记：Map中不存在重复的key，因为放入相同的key，只会把原有的key-value对应的value给替换掉。</p>
<p>此外，在一个Map中，虽然key不能重复，但value是可以重复的。</p>
<h3 id="遍历Map"><a href="#遍历Map" class="headerlink" title="遍历Map"></a>遍历Map</h3><p>对Map来说，要遍历key可以使用for each循环，遍历Map实例的keySet()方法返回的Set集合，它是包含不重复key的集合。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java">Map&lt;String, Integer&gt; map = <span class="hljs-keyword">new</span> HashMap&lt;&gt;();<br>map.put(<span class="hljs-string">"apple"</span>, <span class="hljs-number">123</span>);<br>map.put(<span class="hljs-string">"pear"</span>, <span class="hljs-number">456</span>);<br>map.put(<span class="hljs-string">"banana"</span>, <span class="hljs-number">789</span>);<br><span class="hljs-keyword">for</span> (String key : map.keySet()) {<br>    Integer value = map.get(key);<br>    System.out.println(key + <span class="hljs-string">" = "</span> + value);<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>同时遍历key和value可以遍历Map实例的entrySet()集合，它包含每一个key-value实例。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java">Map&lt;String, Integer&gt; map = <span class="hljs-keyword">new</span> HashMap&lt;&gt;();<br>map.put(<span class="hljs-string">"apple"</span>, <span class="hljs-number">123</span>);<br>map.put(<span class="hljs-string">"pear"</span>, <span class="hljs-number">456</span>);<br>map.put(<span class="hljs-string">"banana"</span>, <span class="hljs-number">789</span>);<br><span class="hljs-keyword">for</span> (Map.Entry&lt;String, Integer&gt; entry : map.entrySet()) {<br>    String key = entry.getKey();<br>    Integer value = entry.getValue();<br>    System.out.println(key + <span class="hljs-string">" = "</span> + value);<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>Map和List不同的是，Map存储的是key-value的映射关系，并且，它不保证顺序。在遍历的时候，遍历的顺序既不一定是put()时放入的key的顺序，也不一定是key的排序顺序。使用Map时，任何依赖顺序的逻辑都是不可靠的。以HashMap为例，假设我们放入”A”，”B”，”C”这3个key，遍历的时候，每个key会保证被遍历一次且仅遍历一次，但顺序完全没有保证，甚至对于不同的JDK版本，相同的代码遍历的输出顺序都是不同的！</p>
<blockquote>
<p>遍历Map时，不可假设输出的key是有序的！</p>
</blockquote>
<h2 id="编写equals和hashCode"><a href="#编写equals和hashCode" class="headerlink" title="编写equals和hashCode"></a>编写equals和hashCode</h2><p>我们知道Map是一种键值映射表，可以通过key快速查找对应的value。以HashMap为例，HashMap之所以能根据key直接拿到value，原因是它内部通过空间换时间的方法，用一个大数组存储所有value，并根据key直接计算出value应该存储在哪个索引。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java">Map&lt;String, Person&gt; map = <span class="hljs-keyword">new</span> HashMap&lt;&gt;();<br>map.put(<span class="hljs-string">"a"</span>, <span class="hljs-keyword">new</span> Person(<span class="hljs-string">"Xiao Ming"</span>));<br>map.put(<span class="hljs-string">"b"</span>, <span class="hljs-keyword">new</span> Person(<span class="hljs-string">"Xiao Hong"</span>));<br>map.put(<span class="hljs-string">"c"</span>, <span class="hljs-keyword">new</span> Person(<span class="hljs-string">"Xiao Jun"</span>));<br><br>map.get(<span class="hljs-string">"a"</span>); <span class="hljs-comment">// Person("Xiao Ming")</span><br>map.get(<span class="hljs-string">"x"</span>); <span class="hljs-comment">// null</span><br></code></pre></td></tr></tbody></table></figure>

<p>如果key的值为”a”，计算得到的索引总是1，因此返回value为Person(“Xiao Ming”)，如果key的值为”b”，计算得到的索引总是5，因此返回value为Person(“Xiao Hong”)，这样，就不必遍历整个数组，即可直接读取key对应的value。</p>
<p>当我们使用key存取value的时候，就会引出一个问题：我们放入Map的key是字符串”a”，但是，当我们获取Map的value时，传入的变量不一定就是放入的那个key对象。换句话讲，两个key应该是内容相同，但不一定是同一个对象。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java">String key1 = <span class="hljs-string">"a"</span>;<br>Map&lt;String, Integer&gt; map = <span class="hljs-keyword">new</span> HashMap&lt;&gt;();<br>map.put(key1, <span class="hljs-number">123</span>);<br><br>String key2 = <span class="hljs-keyword">new</span> String(<span class="hljs-string">"a"</span>);<br>map.get(key2); <span class="hljs-comment">// 123</span><br><br>System.out.println(key1 == key2); <span class="hljs-comment">// false</span><br>System.out.println(key1.equals(key2)); <span class="hljs-comment">// true</span><br></code></pre></td></tr></tbody></table></figure>

<p>因为在Map内部，对key作比较是通过equals()实现的，这一点和List查找元素需要正确覆写equals()是一样的。即正确使用Map必须保证：作为key的对象必须正确覆写了equals()方法。</p>
<p>我们经常使用String作为key，因为String已经正确覆写了equals()。但如果我们放入的key是一个自己写的类，就必须保证正确覆写了equals()。</p>
<p>我们再思考一下HashMap为什么能通过key直接计算出value存储的索引。相同的key对象必须要计算出相同的索引，否则，相同的key每次取出的value就不一定对。通过key计算索引的方式就是调用key对象的hashCode()方法，它返回一个int整数。HashMap正是通过这个方法直接定位key对应的value的索引，继而直接返回value。</p>
<p>因此，正确使用Map必须保证：</p>
<ol>
<li>作为key的对象必须正确覆写equals()方法，相等的两个key实例调用equals()必须返回true</li>
<li>作为key的对象必须正确覆写hashCode()方法，且hashCode()方法要严格遵循以下规范：</li>
</ol>
<ul>
<li>如果两个对象相等，则两个对象的hashCode()必须相等</li>
<li>如果两个对象不相等，则两个对象的hashCode()尽量不要相等</li>
</ul>
<p>上述第一条规范是正确性，必须保证实现，否则HashMap不能正常工作。第二条规范尽量满足，这样可以保证查询效率。因为不同的对象，如果返回相同的hashCode()，会造成Map内部存储冲突，使存储效率下降。</p>
<p>正确编写equals()的方法我们在上两节讲过了，以Person类为例，首先，把需要比较的字段找出来；然后，引用类型使用<code>Objects.equals()</code>比较，基本类型使用<code>==</code>比较。在正确实现equals()的基础上，我们还需要正确实现hashCode()，即上述字段分别相同的实例，hashCode()返回的int必须相同。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Person</span> </span>{<br>    String firstName;<br>    String lastName;<br>    <span class="hljs-keyword">int</span> age;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">hashCode</span><span class="hljs-params">()</span> </span>{<br>        <span class="hljs-keyword">int</span> h = <span class="hljs-number">0</span>;<br>        h = <span class="hljs-number">31</span> * h + firstName.hashCode();<br>        h = <span class="hljs-number">31</span> * h + lastName.hashCode();<br>        h = <span class="hljs-number">31</span> * h + age;<br>        <span class="hljs-keyword">return</span> h;<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>主要到String类已经正确实现了hashCode()方法，我们在计算Person的hashCode()时，反复使用<code>31*h</code>，这样做的目的是为了尽量把不同的Person实例的hashCode()均匀分布在整个int范围。</p>
<p>和实现equals()方法遇到的问题类似，如果firstName或lastName为null，上述代码工作起来就会抛NullPointerException。为了解决这个问题，我们在计算hashCode()的时候，经常借助Objects.hash()来计算。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">hashCode</span><span class="hljs-params">()</span> </span>{<br>    <span class="hljs-keyword">return</span> Objects.hash(firstName, lastName, age);<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>所以，编写equals()和hashCode()遵循的原则是：equals()用到的用于比较的每一字段，都必须在hashCode()中用于计算；equals()中没有使用到的字段，绝不可放在hashCode()中计算。</p>
<p>另外注意，对于放入HashMap的value对象，没有任何要求。</p>
<h3 id="延伸阅读"><a href="#延伸阅读" class="headerlink" title="延伸阅读"></a>延伸阅读</h3><p>既然HashMap内部使用了数组，通过计算key的hashCode()直接定位value所在的索引，那么第一个问题来了：hashCode()返回的int范围高达±21亿，先不考虑负数，HashMap内部使用的数组得有多大？</p>
<p>实际上，HashMap初始化时默认的数组大小只有16，任何key，无论它的hashCode()有多大，都可以简单通过：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">int</span> index = key.hashCode() &amp; <span class="hljs-number">0xf</span>; <span class="hljs-comment">// 0xf = 15</span><br></code></pre></td></tr></tbody></table></figure>

<p>把索引确定再0~15，即永远不会超出数组范围，上述算法只是一种最简单的实现。</p>
<p>第二个问题：如果添加超过16个key-value到HashMap，数组不够用了怎么办？添加超过一定数量的key-value时，HashMap会在内部自动扩容，每次扩容一倍，即数组的长度从16扩展为32，相应的，需要重新确定hashCode()计算的索引位置。例如，对长度为32的数组计算hashCode()对应的索引，计算方式要改为：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">int</span> index = key.hashCode() &amp; <span class="hljs-number">0x1f</span>; <span class="hljs-comment">// 0x1f = 31</span><br></code></pre></td></tr></tbody></table></figure>

<p>由于扩容会导致重新分布已有的key-value，所以频繁扩容对HashMap的性能影响很大。如果我们确定要使用一个容量为10000个key-value的HashMap，更好的方式是创建HashMap时就指定容量：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">Map&lt;String, Integer&gt; map = <span class="hljs-keyword">new</span> HashMap&lt;&gt;(<span class="hljs-number">10000</span>);<br></code></pre></td></tr></tbody></table></figure>

<p>虽然指定容量是10000，但HashMap内部的数组长度总是2^n，因此实际数组长度被初始化为2^14=16384&gt;10000。</p>
<p>最后一个问题：如果不同的两个key，他们的hashCode()恰好是相同的，那后面放的value会不会把前面的value覆盖了？当然不会！使用Map的时候，只要key不相同，它们映射的value就互不干扰。但是，在HashMap内部，确实可能存在不同的key，映射到相同的hashCode()，即相同的数组索引上，肿么办？</p>
<p>我们假设”a”和”b”这两个key最终计算出的索引都是5，那么在HashMap的数组中，实际存储的不是一个Person实例，而是一个包含两个Entry的List，一个是”a”的映射，一个是”b”的映射。在查找的时候，<code>Person p = map.get("a")</code>，HashMap内部通过”a”找到的实际上是List&lt;Entry&lt;String, Person&gt;&gt;，它还需要遍历这个List，并找到key字段是”a”的Entry，再返回对应的Person实例。</p>
<p>我们把不同的key具有相同hashCode()的情况称为哈希冲突。在冲突的时候，一种最简单的解决办法是用List存储hashCode()相同的key-value。显然，如果冲突的概率越大，这个List就越长，Map的get()方法效率就越低，这就解释了为什么要尽量满足第二条规范。</p>
<blockquote>
<p>hashCode()方法编写的越好，HashMap工作的效率越高。实现hashCode()方法可以通过Objects.hashCode()辅助方法实现。</p>
</blockquote>
<h2 id="使用EnumMap"><a href="#使用EnumMap" class="headerlink" title="使用EnumMap"></a>使用EnumMap</h2><p>我们知道，HashMap通过对key计算hashCode()，通过空间换时间的方式，直接定位到value所在的内部数组的索引，因此查询效率非常高。</p>
<p>如果作为key的对象是enum类型，那么还可以使用Java集合库提供的一种EnumMap，它在内部以一个非常紧凑的数组存储value，并且根据enum类型的key直接定位到内部数组的索引，并不需要计算hashCode()，不但效率最高，而且没有额外的空间浪费。</p>
<p>使用EnumMap的时候，我们总是用Map接口来引用它。因此，实际上把HashMap和EnumMap互换，在客户端没有任何差别。</p>
<h2 id="使用TreeMap"><a href="#使用TreeMap" class="headerlink" title="使用TreeMap"></a>使用TreeMap</h2><p>我们知道，HashMap是一种以空间换时间的映射表，它的实现原理决定了内部的key是无序的，即遍历HashMap的key时，其顺序是不可预测的，但每个key都会且仅遍历一次。</p>
<p>还有一种Map，它在内部会对key进行排序，这种Map就是SortedMap，注意SortedMap是接口，其最常用的实现类是TreeMap。</p>
<p>SortedMap保证遍历时以key的顺序来进行排序。例如，放入的Key是”apple”、”pear”、”orange”，遍历的顺序一定是”apple”、”orange”、”pear”，因为String默认按字母排序。</p>
<p>使用TreeMap时，放入的key必须实现Comparable接口。String，Integer这些类已经实现了Comparable接口，因此可以直接作为key使用，作为value的对象则没有任何要求。</p>
<p>如果作为key的class没有实现Comparable接口，那么，必须在创建TreeMap时指定一个自定义排序算法。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">import</span> java.util.*;<br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Main</span> </span>{<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{<br>        Map&lt;Person, Integer&gt; map = <span class="hljs-keyword">new</span> TreeMap&lt;&gt;(<span class="hljs-keyword">new</span> Comparator&lt;Person&gt;() {<br>            <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">compare</span><span class="hljs-params">(Person p1, Person p2)</span> </span>{<br>                <span class="hljs-keyword">return</span> p1.name.compareTo(p2.name);<br>            }<br>        });<br>        map.put(<span class="hljs-keyword">new</span> Person(<span class="hljs-string">"Tom"</span>), <span class="hljs-number">1</span>);<br>        map.put(<span class="hljs-keyword">new</span> Person(<span class="hljs-string">"Bob"</span>), <span class="hljs-number">2</span>);<br>        map.put(<span class="hljs-keyword">new</span> Person(<span class="hljs-string">"Lily"</span>), <span class="hljs-number">3</span>);<br>        <span class="hljs-keyword">for</span> (Person key : map.keySet()) {<br>            System.out.println(key);<br>        }<br>        <span class="hljs-comment">// {Person: Bob}, {Person: Lily}, {Person: Tom}</span><br>        System.out.println(map.get(<span class="hljs-keyword">new</span> Person(<span class="hljs-string">"Bob"</span>))); <span class="hljs-comment">// 2</span><br>    }<br>}<br><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Person</span> </span>{<br>    <span class="hljs-keyword">public</span> String name;<br>    Person(String name) {<br>        <span class="hljs-keyword">this</span>.name = name;<br>    }<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">toString</span><span class="hljs-params">()</span> </span>{<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">"{Person: "</span> + name + <span class="hljs-string">"}"</span>;<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>注意到<code>Comparator</code>接口要求实现一个比较方法，它负责比较传入的两个元素a和b，如果a&lt;b，则返回负数，通常是-1，如果a==b，则返回0，如果a&gt;b，则返回正数，通常是1。TreeMap内部根据比较结果对Key进行排序。</p>
<p>如果要根据Key查找Value，我们可以传入一个<code>new Person("Bob")</code>作为Key，它会返回对应的Integer值<code>2</code>。</p>
<p>另外，注意到Person类并未覆写equals()和hashCode()，因为TreeMap不使用equals()和hashCode()。</p>
<p>我们来看一个稍微复杂的例子，这次我们定义了Student类，并用分数score进行排序。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">import</span> java.util.*;<br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Main</span> </span>{<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{<br>        Map&lt;Student, Integer&gt; map = <span class="hljs-keyword">new</span> TreeMap&lt;&gt;(<span class="hljs-keyword">new</span> Comparator&lt;Student&gt;() {<br>            <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">compare</span><span class="hljs-params">(Student p1, Student p2)</span> </span>{<br>                <span class="hljs-keyword">return</span> p1.score &gt; p2.score ? -<span class="hljs-number">1</span> : <span class="hljs-number">1</span>;<br>            }<br>        });<br>        map.put(<span class="hljs-keyword">new</span> Student(<span class="hljs-string">"Tom"</span>, <span class="hljs-number">77</span>), <span class="hljs-number">1</span>);<br>        map.put(<span class="hljs-keyword">new</span> Student(<span class="hljs-string">"Bob"</span>, <span class="hljs-number">66</span>), <span class="hljs-number">2</span>);<br>        map.put(<span class="hljs-keyword">new</span> Student(<span class="hljs-string">"Lily"</span>, <span class="hljs-number">99</span>), <span class="hljs-number">3</span>);<br>        <span class="hljs-keyword">for</span> (Student key : map.keySet()) {<br>            System.out.println(key);<br>        }<br>        System.out.println(map.get(<span class="hljs-keyword">new</span> Student(<span class="hljs-string">"Bob"</span>, <span class="hljs-number">66</span>))); <span class="hljs-comment">// null?</span><br>    }<br>}<br><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Student</span> </span>{<br>    <span class="hljs-keyword">public</span> String name;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> score;<br>    Student(String name, <span class="hljs-keyword">int</span> score) {<br>        <span class="hljs-keyword">this</span>.name = name;<br>        <span class="hljs-keyword">this</span>.score = score;<br>    }<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">toString</span><span class="hljs-params">()</span> </span>{<br>        <span class="hljs-keyword">return</span> String.format(<span class="hljs-string">"{%s: score=%d}"</span>, name, score);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>在for循环中，我们确实得到了正确的顺序。但是，且慢！根据相同的Key：<code>new Student("Bob", 66)</code>进行查找时，结果为null！这是怎么回事？在这个例子中，TreeMap出现问题，原因出在这个Comparator上。在p1.score和p2.score不相等的时候，它的返回值是正确的，但是，在p1.score和p2.score相等的时候，它并没有返回0！这就是为什么TreeMap工作不正常的原因：TreeMap在比较两个Key是否相等时，依赖Key的compareTo()方法或者<code>Comparator.compare()</code>方法。在两个Key相等时，必须返回0。</p>
<p>我们修改代码如下：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">compare</span><span class="hljs-params">(Student p1, Student p2)</span> </span>{<br>    <span class="hljs-keyword">if</span> (p1.score == p2.score) {<br>        <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>    }<br>    <span class="hljs-keyword">return</span> p1.score &gt; p2.score ? -<span class="hljs-number">1</span> : <span class="hljs-number">1</span>;<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>或者直接借助<code>Integer.compare(int, int)</code>也可以返回正确结果。</p>
<blockquote>
<p>作为SortedMap的Key必须实现Comparable接口，或者传入Comparator；</p>
<p>要严格按照compare()规范实现比较逻辑，否则，TreeMap将不能正常工作。</p>
</blockquote>
<h2 id="使用Properties"><a href="#使用Properties" class="headerlink" title="使用Properties"></a>使用Properties</h2><p>在编写应用程序时，经常需要读写配置文件。例如，用户的设置：</p>
<figure class="highlight plaintext hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs txt"># 上次最后打开的文件:<br>last_open_file=/data/hello.txt<br># 自动保存文件的时间间隔:<br>auto_save_interval=60<br></code></pre></td></tr></tbody></table></figure>

<p>配置文件的特点是，它的key-value一般都是String-String类型的，因此我们完全可以用Map&lt;String, String&gt;来表示。因为配置文件非常有用，所以Java集合库提供了一个Properties来表示一组“配置”。由于历史遗留原因，Properties内部本质上是一个Hashtable，但我们只需要用到Properties自身关于读写配置的接口。</p>
<h3 id="读取配置文件"><a href="#读取配置文件" class="headerlink" title="读取配置文件"></a>读取配置文件</h3><p>用Properties读取配置文件非常简单。Java默认配置文件以<code>.properties</code>为扩展名，每行以<code>key=value</code>表示，以<code>#</code>开头的是注释。可以从文件系统读取上述<code>.properties</code>文件。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java">String f = <span class="hljs-string">"setting.properties"</span>;<br>Properties props = <span class="hljs-keyword">new</span> Properties();<br>props.load(<span class="hljs-keyword">new</span> java.io.FileInputStream(f));<br><br>String filepath = props.getProperty(<span class="hljs-string">"last_open_file"</span>);<br>String interval = props.getProperty(<span class="hljs-string">"auto_save_interval"</span>, <span class="hljs-string">"120"</span>);<br></code></pre></td></tr></tbody></table></figure>

<p>可见，用Properties读取配置文件按，一共有三步：</p>
<ol>
<li>创建Properties实例</li>
<li>调用load()读取文件</li>
<li>调用getProperty()获取配置</li>
</ol>
<p>调用getProperty()获取配置时，如果key不存在，将返回null。我们还可以提供一个默认值，这样，当key不存在的时候，就返回默认值。</p>
<p>也可以从classpath读取<code>.properties</code>文件，因为load(InputStream)方法接受一个InputStream实例，表示一个字节流，它不一定是文件流，也可以是从jar包读取的资源流。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java">Properties props = <span class="hljs-keyword">new</span> Properties();<br>props.load(getClass().getResourceAsStream(<span class="hljs-string">"/common/setting.properties"</span>));<br></code></pre></td></tr></tbody></table></figure>

<p>还可以从内存中读取一个字节流。</p>
<p>如果有多个<code>.properties</code>文件，可以反复调用load()读取，后读取的key-value会覆盖已读取的key-value。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java">Properties props = <span class="hljs-keyword">new</span> Properties();<br>props.load(getClass().getResourceAsStream(<span class="hljs-string">"/common/setting.properties"</span>));<br>props.load(<span class="hljs-keyword">new</span> FileInputStream(<span class="hljs-string">"C:\\conf\\setting.properties"</span>));<br></code></pre></td></tr></tbody></table></figure>

<p>上面的代码演示了Properties的一个常用用法：可以把默认配置文件放到classpath中，然后，根据机器的环境编写另一个配置文件，覆盖某些默认的配置。</p>
<blockquote>
<p>Properties设计的目的是存储String类型的key－value，但Properties实际上是从Hashtable派生的，它的设计实际上是有问题的，但是为了保持兼容性，现在已经没法修改了。除了getProperty()和setProperty()方法外，还有从Hashtable继承下来的get()和put()方法，这些方法的参数签名是Object，我们在使用Properties的时候，不要去调用这些从Hashtable继承下来的方法。</p>
</blockquote>
<h3 id="写入配置文件"><a href="#写入配置文件" class="headerlink" title="写入配置文件"></a>写入配置文件</h3><p>如果通过setProperty()修改了Properties实例，可以把配置写入文件，以便下次启动时获得最新配置。写入配置文件使用store()方法。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java">Properties props = <span class="hljs-keyword">new</span> Properties();<br>props.setProperty(<span class="hljs-string">"url"</span>, <span class="hljs-string">"http://www.liaoxuefeng.com"</span>);<br>props.setProperty(<span class="hljs-string">"language"</span>, <span class="hljs-string">"Java"</span>);<br>props.store(<span class="hljs-keyword">new</span> FileOutputStream(<span class="hljs-string">"C:\\conf\\setting.properties"</span>), <span class="hljs-string">"这是写入的properties注释"</span>);<br></code></pre></td></tr></tbody></table></figure>

<h3 id="编码"><a href="#编码" class="headerlink" title="编码"></a>编码</h3><p>早期版本的Java规定<code>.properties</code>文件编码是ASCII编码（ISO8859-1），如果涉及到中文就必须用<code>name=\u4e2d\u6587</code>来表示，非常别扭。从JDK9开始，Java的<code>.properties</code>文件可以使用UTF-8编码了。</p>
<p>不过，需要注意的是，由于load(InputStream)默认总是以ASCII编码读取字节流，所以会导致读到乱码。我们需要用另一个重载方法load(Reader)读取：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java">Properties props = <span class="hljs-keyword">new</span> Properties();<br>props.load(<span class="hljs-keyword">new</span> FileReader(<span class="hljs-string">"settings.properties"</span>, StandardCharsets.UTF_8));<br></code></pre></td></tr></tbody></table></figure>

<p>就可以正常读取中文。InputStream和Reader的区别是一个是字节流，一个是字符流。字符流在内存中已经以char类型表示了，不涉及编码问题。</p>
<h2 id="使用Set"><a href="#使用Set" class="headerlink" title="使用Set"></a>使用Set</h2><p>我们知道，Map用于存储key-value的映射，对于充当key的对象，是不能重复的，并且，不但需要正确覆写equals()方法，还要正确覆写hashCode()方法。</p>
<p>如果我们只需要存储不重复的key，并不需要存储映射的value，那么就可以使用Set。Set用于存储不重复元素的集合，它主要提供以下几个方法：</p>
<ul>
<li>将元素添加进Set<e>：boolean add(E e)</e></li>
<li>将元素从Set<e>删除：boolean remove(Object e)</e></li>
<li>判断是否包含元素：boolean contains(Object e)</li>
</ul>
<p>Set实际上相当于只存储key，不存储value的Map。我们经常使用Set用于去除重复元素。因为放入Set的元素和Map的key类似，都要正确实现equals()和hashCode()方法，否则该元素无法正确的放入Set。最常用的Set的实现类是HashSet，实际上，HashSet仅仅是对HashMap的一个简单封装，它的核心代码如下：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HashSet</span>&lt;<span class="hljs-title">E</span>&gt; <span class="hljs-keyword">implements</span> <span class="hljs-title">Set</span>&lt;<span class="hljs-title">E</span>&gt; </span>{<br>    <span class="hljs-comment">// 持有一个HashMap:</span><br>    <span class="hljs-keyword">private</span> HashMap&lt;E, Object&gt; map = <span class="hljs-keyword">new</span> HashMap&lt;&gt;();<br><br>    <span class="hljs-comment">// 放入HashMap的value:</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Object PRESENT = <span class="hljs-keyword">new</span> Object();<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">add</span><span class="hljs-params">(E e)</span> </span>{<br>        <span class="hljs-keyword">return</span> map.put(e, PRESENT) == <span class="hljs-keyword">null</span>;<br>    }<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">contains</span><span class="hljs-params">(Object o)</span> </span>{<br>        <span class="hljs-keyword">return</span> map.containsKey(o);<br>    }<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">remove</span><span class="hljs-params">(Object o)</span> </span>{<br>        <span class="hljs-keyword">return</span> map.remove(o) == PRESENT;<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>Set接口不保证有序，而SortedSet保证元素是有序的。HashSet是无序的，因为它实现了Set接口，并没有实现SortedSet接口；TreeSet是有序的，因为它实现了SortedSet接口。</p>
<p>使用TreeSet和使用TreeMap的要求一样，添加的元素必须正确实现Comparable接口，如果没有实现Comparable接口，那么创建TreeSet时必须传入一个Comparator对象。</p>
<h2 id="使用Queue"><a href="#使用Queue" class="headerlink" title="使用Queue"></a>使用Queue</h2><p>队列（Queue）是一种经常使用的集合。Queue实际上是实现了一个先进先出的有序表。它和List的区别在于，List可以在任意位置添加和删除元素，而Queue只有两个操作：</p>
<ul>
<li>把元素添加到队列末尾</li>
<li>从队列头部取出元素</li>
</ul>
<p>在Java标准库中，队列接口Queue定义了以下几个方法：</p>
<ul>
<li>int size()，获取队列长度</li>
<li>boolean add(E)/boolean offer(E)，添加元素到队尾</li>
<li>E remove()/E poll()，获取队首元素并从队列中删除</li>
<li>E element()/E peek()，获取队首元素但并不从队列中删除</li>
</ul>
<p>对于具体的实现类，有的Queue有最大队列长度限制，有的Queue没有。注意到添加、删除和获取队首元素总是有两个方法，这是因为在添加和删除失败时，这两个方法的行为是不同的。</p>
<table>
<thead>
<tr>
<th align="center">操作</th>
<th align="center">throw Exception</th>
<th align="center">返回false或null</th>
</tr>
</thead>
<tbody><tr>
<td align="center">添加元素到队尾</td>
<td align="center">add(E e)</td>
<td align="center">boolean offer(E e)</td>
</tr>
<tr>
<td align="center">取队首元素并删除</td>
<td align="center">E remove()</td>
<td align="center">E poll()</td>
</tr>
<tr>
<td align="center">取队首元素但不删除</td>
<td align="center">E element()</td>
<td align="center">E peek()</td>
</tr>
</tbody></table>
<p>因此，两套方法可以根据需要来选择使用。注意，不要把null添加到队列中，不然很难确定是取到了null元素还是队列为空。</p>
<p>LinkedList即实现了List接口，又实现了Queue接口，使用时，把它当作List就获取List的引用，把它当作Queue就获取Queue的引用。始终按照面向抽象编程的原则编写代码，可以大大提高代码质量。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 这是一个List:</span><br>List&lt;String&gt; list = <span class="hljs-keyword">new</span> LinkedList&lt;&gt;();<br><span class="hljs-comment">// 这是一个Queue:</span><br>Queue&lt;String&gt; queue = <span class="hljs-keyword">new</span> LinkedList&lt;&gt;();<br></code></pre></td></tr></tbody></table></figure>

<h2 id="使用PriorityQueue"><a href="#使用PriorityQueue" class="headerlink" title="使用PriorityQueue"></a>使用PriorityQueue</h2><p>我们知道，Queue是一个先进先出的队列。但当我们要实现“VIP插队”的业务，用Queue就不行了，因为Queue会严格遵循FIFO的原则，这时我们就需要优先队列PriorityQueue。</p>
<p>PriorityQueue和Queue的区别在于，它的出队顺序与元素的优先级相关，对PriorityQueue调用remove()或poll()方法，返回的总是优先级最高的元素。因此，要使用PriorityQueue，就必须给每个元素定义优先级。</p>
<p>因此，放入PriorityQueue的元素，必须实现Comparable接口，PriorityQueue会根据元素的排序顺序决定出队的优先级。如果我们要放入的元素并没有实现Comparable接口怎么办？PriorityQueue允许我们提供一个Comparator对象来判断两个元素的顺序。我们以银行排队业务为例：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">import</span> java.util.Comparator;<br><span class="hljs-keyword">import</span> java.util.PriorityQueue;<br><span class="hljs-keyword">import</span> java.util.Queue;<br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Main</span> </span>{<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{<br>        Queue&lt;User&gt; q = <span class="hljs-keyword">new</span> PriorityQueue&lt;&gt;(<span class="hljs-keyword">new</span> UserComparator());<br>        <span class="hljs-comment">// 添加3个元素到队列:</span><br>        q.offer(<span class="hljs-keyword">new</span> User(<span class="hljs-string">"Bob"</span>, <span class="hljs-string">"A1"</span>));<br>        q.offer(<span class="hljs-keyword">new</span> User(<span class="hljs-string">"Alice"</span>, <span class="hljs-string">"A2"</span>));<br>        q.offer(<span class="hljs-keyword">new</span> User(<span class="hljs-string">"Boss"</span>, <span class="hljs-string">"V1"</span>));<br>        System.out.println(q.poll()); <span class="hljs-comment">// Boss/V1</span><br>        System.out.println(q.poll()); <span class="hljs-comment">// Bob/A1</span><br>        System.out.println(q.poll()); <span class="hljs-comment">// Alice/A2</span><br>        System.out.println(q.poll()); <span class="hljs-comment">// null,因为队列为空</span><br>    }<br>}<br><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserComparator</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Comparator</span>&lt;<span class="hljs-title">User</span>&gt; </span>{<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">compare</span><span class="hljs-params">(User u1, User u2)</span> </span>{<br>        <span class="hljs-keyword">if</span> (u1.number.charAt(<span class="hljs-number">0</span>) == u2.number.charAt(<span class="hljs-number">0</span>)) {<br>            <span class="hljs-comment">// 如果两人的号都是A开头或者都是V开头,比较号的大小:</span><br>            <span class="hljs-keyword">return</span> u1.number.compareTo(u2.number);<br>        }<br>        <span class="hljs-keyword">if</span> (u1.number.charAt(<span class="hljs-number">0</span>) == <span class="hljs-string">'V'</span>) {<br>            <span class="hljs-comment">// u1的号码是V开头,优先级高:</span><br>            <span class="hljs-keyword">return</span> -<span class="hljs-number">1</span>;<br>        } <span class="hljs-keyword">else</span> {<br>            <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;<br>        }<br>    }<br>}<br><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> </span>{<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> String name;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> String number;<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">User</span><span class="hljs-params">(String name, String number)</span> </span>{<br>        <span class="hljs-keyword">this</span>.name = name;<br>        <span class="hljs-keyword">this</span>.number = number;<br>    }<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">toString</span><span class="hljs-params">()</span> </span>{<br>        <span class="hljs-keyword">return</span> name + <span class="hljs-string">"/"</span> + number;<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>实现PriorityQueue的关键在于提供的UserComparator对象，它负责比较两个元素的大小（较小的在前）。UserComparator总是把V开头的号码优先返回，只有在开头相同的时候，才比较号码大小。</p>
<h2 id="使用Deque"><a href="#使用Deque" class="headerlink" title="使用Deque"></a>使用Deque</h2><p>我们知道，Queue是队列，只能一头进一头出。如果把条件放松，允许两头都进两头都出，这种队列叫双端队列（Double Ended Queue），学名Deque。Java集合提供了接口Deque来实现一个双端队列，它的功能：</p>
<ul>
<li>既可以添加到队尾，也可以添加到队首</li>
<li>既可以从队首获取，又可以从队尾获取</li>
</ul>
<p>我们来比较一下Queue和Deque出队和入队的方法：</p>
<table>
<thead>
<tr>
<th align="center"></th>
<th align="center">Queue</th>
<th align="center">Deque</th>
</tr>
</thead>
<tbody><tr>
<td align="center">添加元素到队尾</td>
<td align="center">add(E e) / offer(E e)</td>
<td align="center">addLast(E e) / offerLast(E e)</td>
</tr>
<tr>
<td align="center">取队首元素并删除</td>
<td align="center">E remove() / E poll()</td>
<td align="center">E removeFirst() / E pollFirst()</td>
</tr>
<tr>
<td align="center">取队首元素但不删除</td>
<td align="center">E element() / E peek()</td>
<td align="center">E getFirst() / E peekFirst()</td>
</tr>
<tr>
<td align="center">添加元素到队首</td>
<td align="center">无</td>
<td align="center">addFirst(E e) / offerFirst(E e)</td>
</tr>
<tr>
<td align="center">取队尾元素并删除</td>
<td align="center">无</td>
<td align="center">E removeLast() / E pollLast()</td>
</tr>
<tr>
<td align="center">取队尾元素但不删除</td>
<td align="center">无</td>
<td align="center">E getLast() / E peekLast()</td>
</tr>
</tbody></table>
<p>注意到Deque接口实际上扩展自Queue，因此，Queue提供的add()/offer()方法在Deque中也能用，但是使用Deque最好不要调用offer()，而是调用offerLast()。使用Deque，推荐总是明确调用offerLast()/offerFirst()或者pollFirst()/pollLast()方法。</p>
<p>Deque是一个接口，它的实现类有ArrayDeque和LinkedList。我们发现LinkedList真是一个全能选手，它即是List，又是Queue，还是Deque。但是我们在使用的时候，总是用特定的接口来引用它，这是因为持有接口说明代码的抽象层次更高，而且接口本身定义的方法代表了特定的用途。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 不推荐的写法:</span><br>LinkedList&lt;String&gt; d1 = <span class="hljs-keyword">new</span> LinkedList&lt;&gt;();<br>d1.offerLast(<span class="hljs-string">"z"</span>);<br><span class="hljs-comment">// 推荐的写法：</span><br>Deque&lt;String&gt; d2 = <span class="hljs-keyword">new</span> LinkedList&lt;&gt;();<br>d2.offerLast(<span class="hljs-string">"z"</span>);<br></code></pre></td></tr></tbody></table></figure>

<h2 id="使用Stack"><a href="#使用Stack" class="headerlink" title="使用Stack"></a>使用Stack</h2><p>栈（Stack）是一种后进先出的数据结构，只能不断的往Stack中压入（push）元素，最后进去的必须最早弹出（pop）来。Stack只有入栈和出栈操作：</p>
<ul>
<li>把元素压栈，push(E)</li>
<li>把栈顶元素弹出，pop()</li>
<li>取栈顶元素但不弹出，peek()</li>
</ul>
<p>在Java中，我们用Deque可以实现Stack的功能：</p>
<ul>
<li>把元素压栈：push(E)/addFirst(E)</li>
<li>把栈顶的元素弹出：pop()/removeFirst()</li>
<li>取栈顶元素但不弹出：peek()/peekFirst()</li>
</ul>
<p>为什么Java的集合类没有单独的Stack接口呢？因为有个遗留类的名字就叫Stack，出于兼容性考虑，所以没办法创建Stack接口，只能用Deque接口来模拟一个Stack了。当我们把Deque当Stack来用时，主意只调用push()/pop()/peek()方法，不要调用addFirst()/removeFirst()/peekFirst()方法，这样使代码更加清晰。</p>
<h3 id="Stack的作用"><a href="#Stack的作用" class="headerlink" title="Stack的作用"></a>Stack的作用</h3><p>Stack在计算机中使用非常广泛，JVM在<strong>处理Java方法调用</strong>的时候就会通过栈这种数据结构维护方法调用的层次。JVM会创建方法调用栈，每调用一个方法时，先将参数压栈，然后执行对应的方法；当方法返回时，返回值压栈，调用方法通过出栈操作获得方法返回值。</p>
<p>因为方法调用栈有容量限制，嵌套调用太多会造成栈溢出，即引发StackOverflowError。</p>
<p><strong>对整数进行进制转换</strong>就可以利用栈。</p>
<p><strong>中缀表达式转为后缀表达式</strong>会用到栈。<strong>计算后缀表达式</strong>也会用到栈。</p>
<h2 id="使用Iterator"><a href="#使用Iterator" class="headerlink" title="使用Iterator"></a>使用Iterator</h2><p>Java的集合类都可以使用for each循环，List、Set和Queue会迭代每个元素，Map会迭代每个key。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java">List&lt;String&gt; list = List.of(<span class="hljs-string">"Apple"</span>, <span class="hljs-string">"Orange"</span>, <span class="hljs-string">"Pear"</span>);<br><span class="hljs-keyword">for</span> (String s : list) {<br>    System.out.println(s);<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>实际上，Java编译器并不知道如何遍历List，上述代码能够编译通过，只是因为编译器把for each循环通过Iterator写成了普通的for循环：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">for</span> (Iterator&lt;String&gt; it = list.iterator(); it.hasNext(); ) {<br>     String s = it.next();<br>     System.out.println(s);<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>我们把这种通过Iterator对象遍历集合的模式称为迭代器。使用迭代器的好处在于，调用方总是以统一的方式遍历各种集合类型，而不必关心他们内部的存储结构。</p>
<p>例如，我们虽然知道ArrayList在内部是以数组形式存储元素，并且它还提供了get(int)方法。虽然我们可以用for循环遍历，但是这样一来，调用方就必须知道集合的内部存储结构。并且，如果把ArrayList换成LinkedList，get(int)方法耗时会随着index的增加而增加。如果把ArrayList换为Set，上述代码就无法编译，因为Set内部没有索引。</p>
<p>用Iterator遍历就没有上述问题，因为Iterator对象是集合在自己内部创建的，它自己知道如何高效遍历内部的数据集合，调用方则获得了统一的代码，编译器才能把标准的for each循环自动转换成Iterator遍历。</p>
<p>如果我们自己编写了一个集合类，想要使用for each循环，只需满足下面的条件：</p>
<ul>
<li>集合类实现Iterator接口，该接口要求返回一个Iterator对象</li>
<li>用Iterator对象迭代集合内部数据</li>
</ul>
<p>这里的关键在于，集合类通过调用iterator()方法，返回一个Iterator对象，这个对象必须自己知道如何遍历该集合。一个简单的Iterator示例如下，它总是以倒序遍历集合：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">import</span> java.util.*;<br><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Main</span> </span>{<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{<br>        ReverseList&lt;String&gt; rlist = <span class="hljs-keyword">new</span> ReverseList&lt;&gt;();<br>        rlist.add(<span class="hljs-string">"Apple"</span>);<br>        rlist.add(<span class="hljs-string">"Orange"</span>);<br>        rlist.add(<span class="hljs-string">"Pear"</span>);<br>        <span class="hljs-keyword">for</span> (String s : rlist) {<br>            System.out.println(s);<br>        }<br>    }<br>}<br><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ReverseList</span>&lt;<span class="hljs-title">T</span>&gt; <span class="hljs-keyword">implements</span> <span class="hljs-title">Iterable</span>&lt;<span class="hljs-title">T</span>&gt; </span>{<br><br>    <span class="hljs-keyword">private</span> List&lt;T&gt; list = <span class="hljs-keyword">new</span> ArrayList&lt;&gt;();<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">add</span><span class="hljs-params">(T t)</span> </span>{<br>        list.add(t);<br>    }<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> Iterator&lt;T&gt; <span class="hljs-title">iterator</span><span class="hljs-params">()</span> </span>{<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ReverseIterator(list.size());<br>    }<br><br>    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ReverseIterator</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Iterator</span>&lt;<span class="hljs-title">T</span>&gt; </span>{<br>        <span class="hljs-keyword">int</span> index;<br><br>        ReverseIterator(<span class="hljs-keyword">int</span> index) {<br>            <span class="hljs-keyword">this</span>.index = index;<br>        }<br><br>        <span class="hljs-meta">@Override</span><br>        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">hasNext</span><span class="hljs-params">()</span> </span>{<br>            <span class="hljs-keyword">return</span> index &gt; <span class="hljs-number">0</span>;<br>        }<br><br>        <span class="hljs-meta">@Override</span><br>        <span class="hljs-function"><span class="hljs-keyword">public</span> T <span class="hljs-title">next</span><span class="hljs-params">()</span> </span>{<br>            index--;<br>            <span class="hljs-keyword">return</span> ReverseList.<span class="hljs-keyword">this</span>.list.get(index);<br>        }<br>    }<br>}<br><br></code></pre></td></tr></tbody></table></figure>

<p>虽然ReverseList和ReverseIterator的实现类稍微比较复杂，但是，注意到这是底层集合库，只需编写一次。而调用方则完全按for each循环编写代码，根本不需要知道集合内部的存储逻辑和遍历逻辑。</p>
<blockquote>
<p>在编写Iterator的时候，我们通常可以用一个内部类来实现Iterator接口，这个内部类可以直接访问对应的外部类的所有字段和方法。例如，上述代码中，内部类ReverseIterator可以用<code>ReverseList.this</code>获得当前外部类的this引用，然后，通过这个this引用就可以访问ReverseList的所有字段和方法。</p>
</blockquote>
<h2 id="使用Collections"><a href="#使用Collections" class="headerlink" title="使用Collections"></a>使用Collections</h2><p>Collections是JDK提供的工具类，同样位于<code>java.util</code>包中，它提供了一系列静态方法，能更方便的操作各种集合。</p>
<p>我们一般看方法名和参数就可以确认<code>Collections</code>提供的该方法的功能。例如，对于以下静态方法：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">addAll</span><span class="hljs-params">(Collection&lt;? <span class="hljs-keyword">super</span> T&gt; c, T... elements)</span> </span>{ ... }<br></code></pre></td></tr></tbody></table></figure>

<p>addAll()方法可以给一个Collection类型的集合添加若干元素。因为方法签名是Collection，所以我们可以传入List，Set等各种集合类型。</p>
<h3 id="创建空集合"><a href="#创建空集合" class="headerlink" title="创建空集合"></a>创建空集合</h3><p>Collections提供了一系列方法来创建空集合：</p>
<ul>
<li>创建空List：<code>List&lt;T&gt; emptyList()</code></li>
<li>创建空Map：<code>Map&lt;K, V&gt; emptyMap()</code></li>
<li>创建空Set：<code>Set&lt;T&gt; emptySet()</code></li>
</ul>
<p>注意，返回的空集合是不可变集合，无法向其中添加或删除元素。</p>
<p>此外，也可以用各个集合接口提供的<code>of(T...)</code>方法创建空集合，以下两个创建空List的方法是等价的。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java">List&lt;String&gt; list1 = List.of();<br>List&lt;String&gt; list2 = Collections.emptyList();<br></code></pre></td></tr></tbody></table></figure>

<h3 id="创建单元素集合"><a href="#创建单元素集合" class="headerlink" title="创建单元素集合"></a>创建单元素集合</h3><p>Collections提供了一系列方法来创建一个单元素集合：</p>
<ul>
<li>创建一个元素的List：<code>List&lt;T&gt; singletonList(T o)</code></li>
<li>创建一个元素的Map：<code>Map&lt;K, V&gt; singletonMap(K key, V value)</code></li>
<li>创建一个元素的Set：<code>Set&lt;T&gt; singleton(T o)</code></li>
</ul>
<p>要注意到返回的单元素集合也是不可变集合，无法向其中添加或删除元素。</p>
<p>此外，也可以用各个集合接口提供的<code>of(T...)</code>方法创建单元素集合。例如，以下创建单元素<code>List</code>的两个方法是等价的：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java">List&lt;String&gt; list1 = List.of(<span class="hljs-string">"apple"</span>);<br>List&lt;String&gt; list2 = Collections.singletonList(<span class="hljs-string">"apple"</span>);<br></code></pre></td></tr></tbody></table></figure>

<p>实际上，使用<code>List.of(T...)</code>更方便，因为它既可以创建空集合，也可以创建单元素集合，还可以创建任意个元素的集合：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java">List&lt;String&gt; list1 = List.of(); <span class="hljs-comment">// empty list</span><br>List&lt;String&gt; list2 = List.of(<span class="hljs-string">"apple"</span>); <span class="hljs-comment">// 1 element</span><br>List&lt;String&gt; list3 = List.of(<span class="hljs-string">"apple"</span>, <span class="hljs-string">"pear"</span>); <span class="hljs-comment">// 2 elements</span><br>List&lt;String&gt; list4 = List.of(<span class="hljs-string">"apple"</span>, <span class="hljs-string">"pear"</span>, <span class="hljs-string">"orange"</span>); <span class="hljs-comment">// 3 elements</span><br></code></pre></td></tr></tbody></table></figure>

<h3 id="排序"><a href="#排序" class="headerlink" title="排序"></a>排序</h3><p>Collections可以对List进行排序，方法是Collections.sort(List)，因为排序会直接修改List元素的位置，因此必须传入可变List。</p>
<h3 id="洗牌"><a href="#洗牌" class="headerlink" title="洗牌"></a>洗牌</h3><p>Collections提供了洗牌算法，即传入一个有序的List，可以随机打乱List内部元素的顺序，方法是Collections.shuffle(List)，效果相当于让计算机洗牌。</p>
<h3 id="不可变集合"><a href="#不可变集合" class="headerlink" title="不可变集合"></a>不可变集合</h3><p>Collections还提供了一组方法把可变集合封装成不可变集合，Collections.unmodifiableList(List)。</p>
<ul>
<li>封装成不可变List：<code>List&lt;T&gt; unmodifiableList(List&lt;? extends T&gt; list)</code></li>
<li>封装成不可变Set：<code>Set&lt;T&gt; unmodifiableSet(Set&lt;? extends T&gt; set)</code></li>
<li>封装成不可变Map：<code>Map&lt;K, V&gt; unmodifiableMap(Map&lt;? extends K, ? extends V&gt; m)</code></li>
</ul>
<p>这种封装实际上是通过创建一个代理对象，拦截掉所有修改方法实现的。然而，继续对原始的可变List进行增删是可以的，并且，可以直接影响到封装后“不可变的”List。因此，如果我们希望把一个可变List封装成不可变List，那么返回不可变List后，最好立刻扔掉可变List的引用，这样可以保证后续操作不会意外改变原始对象，从而造成“不可变的”List发生变化。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java">List&lt;String&gt; mutable = <span class="hljs-keyword">new</span> ArrayList&lt;&gt;();<br>mutable.add(<span class="hljs-string">"apple"</span>);<br>mutable.add(<span class="hljs-string">"pear"</span>);<br><span class="hljs-comment">// 变为不可变集合:</span><br>List&lt;String&gt; immutable = Collections.unmodifiableList(mutable);<br><span class="hljs-comment">// 立刻扔掉mutable的引用:</span><br>mutable = <span class="hljs-keyword">null</span>;<br>System.out.println(immutable);<br></code></pre></td></tr></tbody></table></figure>

<h3 id="线程安全集合"><a href="#线程安全集合" class="headerlink" title="线程安全集合"></a>线程安全集合</h3><p>Collections还提供了一组方法，可以把线程不安全的集合变为线程安全的集合：</p>
<ul>
<li>变为线程安全的List：<code>List&lt;T&gt; synchronizedList(List&lt;T&gt; list)</code></li>
<li>变为线程安全的Set：<code>Set&lt;T&gt; synchronizedSet(Set&lt;T&gt; s)</code></li>
<li>变为线程安全的Map：<code>Map&lt;K,V&gt; synchronizedMap(Map&lt;K,V&gt; m)</code></li>
</ul>
<p>多线程的概念我们会在后面讲。因为从Java 5开始，引入了更高效的并发集合类，所以上述这几个同步方法已经没有什么用了。</p>
</body></html>
    
    </div>
    
    
    <div class="columns is-mobile is-multiline article-nav">
        <span class="column is-12-mobile is-half-desktop  article-nav-prev">
            
            <a href="/Study/Java/zh-cn/IO/">IO</a>
            
        </span>
        <span class="column is-12-mobile is-half-desktop  article-nav-next">
            
            <a href="/Study/Java/zh-cn/%E6%B3%9B%E5%9E%8B/">泛型</a>
            
        </span>
    </div>
    
</article>


<div class="sharebox">
    
<div class="sharethis-inline-share-buttons"></div>
<script type='text/javascript' src='//platform-api.sharethis.com/js/sharethis.js#property=608c1408daac690012507aa2&amp;product=sop' async='async'></script>

</div>



    </div>
</section>
    <footer class="footer">
    <div class="container">
        <div class="columns content">
            <div class="column is-narrow has-text-centered">
                &copy; 2021 wanzixin&nbsp;
                Powered by <a href="http://hexo.io/" target="_blank">Hexo</a> & <a
                        target="_blank" rel="noopener" href="http://github.com/ppoffice/hexo-theme-minos">Minos</a>
            </div>
            <div class="column is-hidden-mobile"></div>

            
            <div class="column is-narrow">
                <div class="columns is-mobile is-multiline is-centered">
                
                    
                <a class="column is-narrow has-text-black" title="GitHub" target="_blank" rel="noopener" href="https://github.com/ppoffice/hexo-theme-minos">
                    
                    GitHub
                    
                </a>
                
                </div>
            </div>
            
            
<div class="column is-narrow has-text-centered">
    <div class="dropdown is-up is-right is-hoverable" style="margin-top: -0.2em;">
        <div class="dropdown-trigger">
            <button class="button is-small" aria-haspopup="true" aria-controls="dropdown-menu7">
                <span class="icon">
                    <i class="fas fa-globe"></i>
                </span>
                <span>简体中文</span>
                <span class="icon is-small">
            <i class="fas fa-angle-down" aria-hidden="true"></i>
          </span>
            </button>
        </div>
        <div class="dropdown-menu has-text-left" role="menu">
            <div class="dropdown-content">
            
                <a href="/Study/Java/zh-cn/%E9%9B%86%E5%90%88/" class="dropdown-item">
                    English
                </a>
            
                <a href="/zh-cn/Study/Java/zh-cn/%E9%9B%86%E5%90%88/" class="dropdown-item">
                    简体中文
                </a>
            
            </div>
        </div>
    </div>
</div>

        </div>
    </div>
</footer>
    <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment-with-locales.min.js"></script>

<!-- test if the browser is outdated -->
<div id="outdated">
    <h6>Your browser is out-of-date!</h6>
    <p>Update your browser to view this website correctly. <a id="btnUpdateBrowser" target="_blank" rel="noopener" href="http://outdatedbrowser.com/">Update my browser now </a></p>
    <p class="last"><a href="#" id="btnCloseUpdateBrowser" title="Close">&times;</a></p>
</div>
<script src="//cdnjs.cloudflare.com/ajax/libs/outdated-browser/1.1.5/outdatedbrowser.min.js"></script>
<script>
    $(document).ready(function () {
        // plugin function, place inside DOM ready function
        outdatedBrowser({
            bgColor: '#f25648',
            color: '#ffffff',
            lowerThan: 'flex'
        })
    });
</script>

<script>
    window.FontAwesomeConfig = {
        searchPseudoElements: true
    }
    moment.locale("zh-CN");
</script>


    
    
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.4/MathJax.js?config=TeX-MML-AM_CHTML"></script>
<script>
    MathJax.Hub.Config({
        "HTML-CSS": {
            matchFontHeight: false
        },
        SVG: {
            matchFontHeight: false
        },
        CommonHTML: {
            matchFontHeight: false
        },
        tex2jax: {
            inlineMath: [
                ['$','$'],
                ['\\(','\\)']
            ]
        }
    });
</script>

    
    
    
    
<script src="//cdnjs.cloudflare.com/ajax/libs/lightgallery/1.6.8/js/lightgallery-all.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/justifiedGallery/3.6.5/js/jquery.justifiedGallery.min.js"></script>
<script>
    (function ($) {
        $(document).ready(function () {
            if (typeof($.fn.lightGallery) === 'function') {
                $('.article.gallery').lightGallery({ selector: '.gallery-item' });
            }
            if (typeof($.fn.justifiedGallery) === 'function') {
                $('.justified-gallery').justifiedGallery();
            }
        });
    })(jQuery);
</script>

    
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js"></script>
    <style>
        .hljs {
            position: relative;
        }

        .hljs .clipboard-btn {
            float: right;
            color: #9a9a9a;
            background: none;
            border: none;
            cursor: pointer;
        }

        .hljs .clipboard-btn:hover {
          color: #8a8a8a;
        }

        .hljs > .clipboard-btn {
            display: none;
            position: absolute;
            right: 4px;
            top: 4px;
        }

        .hljs:hover > .clipboard-btn {
            display: inline;
        }

        .hljs > figcaption > .clipboard-btn {
            margin-right: 4px;
        }
    </style>
    <script>
      $(document).ready(function () {
        $('figure.hljs').each(function(i, figure) {
          var codeId = 'code-' + i;
          var code = figure.querySelector('.code');
          var copyButton = $('<button>Copy <i class="far fa-clipboard"></i></button>');
          code.id = codeId;
          copyButton.addClass('clipboard-btn');
          copyButton.attr('data-clipboard-target-id', codeId);

          var figcaption = figure.querySelector('figcaption');

          if (figcaption) {
            figcaption.append(copyButton[0]);
          } else {
            figure.prepend(copyButton[0]);
          }
        })

        var clipboard = new ClipboardJS('.clipboard-btn', {
          target: function(trigger) {
            return document.getElementById(trigger.getAttribute('data-clipboard-target-id'));
          }
        });
        clipboard.on('success', function(e) {
          e.clearSelection();
        })
      })
    </script>

    
    

    



<script src="/js/script.js"></script>


    
    <div class="searchbox ins-search">
    <div class="searchbox-mask"></div>
    <div class="searchbox-container ins-search-container">
        <div class="searchbox-input-wrapper">
            <input type="text" class="searchbox-input ins-search-input" placeholder="站内搜索" />
            <span class="searchbox-close ins-close ins-selectable"><i class="fa fa-times-circle"></i></span>
        </div>
        <div class="searchbox-result-wrapper ins-section-wrapper">
            <div class="ins-section-container"></div>
        </div>
    </div>
</div>
<script>
    (function (window) {
        var INSIGHT_CONFIG = {
            TRANSLATION: {
                POSTS: '文章',
                PAGES: '页面',
                CATEGORIES: '分类',
                TAGS: '标签',
                UNTITLED: '(无标题)',
            },
            CONTENT_URL: '/content.zh-cn.json',
        };
        window.INSIGHT_CONFIG = INSIGHT_CONFIG;
    })(window);
</script>

<script src="/js/insight.js"></script>

    
</body>
</html>